package core.reports;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import common.TimeSource;
import common.util.DateUtils;

import persistence.DataPersistor;
import core.Container;
import core.ContainerFacade;
import core.Item;
import core.ItemFacade;
import core.Product;
import core.ProductFacade;
import core.ProductManager;


public class RoyceProductStatsTest {

	private static ContainerFacade c_f;
	private static ItemFacade i_f;
	private static ProductFacade p_f;
	private static Random rand;
	private static String ALPHA;
	private static int PbarcodeCounter;
	private static Calendar now;
	private static Container SU;
	
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    	ALPHA = "abcdefghijklmnopqrstuvwxyz";
    	rand = new Random();
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    	
    }

    @Before
    public void setUp() throws Exception {
		DataPersistor.instance().init("-serial");
		ContainerFacade.clearAllData();
		ItemFacade.clearAllData();
		ProductFacade.clearAllData();
		c_f = ContainerFacade.instance();
		i_f = ItemFacade.instance();
		p_f = ProductFacade.instance();
		TimeSource.instance().reset();
		PbarcodeCounter = 0;
		now = Calendar.getInstance();
		SU = c_f.addStorageUnit("SU");
    }
    
    @After
    public void tearDown() throws Exception {
    	
    }
    
    @Test
    public void test15efgh() throws Exception {
    	final int MONTHS = 50;
    	int lastYear = now.get(Calendar.YEAR) - 1;
    	
    	Calendar day01 = makeCalendar(lastYear, Calendar.SEPTEMBER, 1);
    	Calendar day02 = makeCalendar(lastYear, Calendar.SEPTEMBER, 2);
    	Calendar day03 = makeCalendar(lastYear, Calendar.SEPTEMBER, 3);
    	Calendar day10 = makeCalendar(lastYear, Calendar.SEPTEMBER, 10);
    	
    	TimeSource.instance().setTime(day01);
    	Product pInt = new Product(getNextPBarcode(), "1_integer cur average test", "1",
    								"count", 3, 3);
    	Product pOne = new Product(getNextPBarcode(), "2_one sig digit cur average test", "1",
    								"count", 3, 3);
    	Product pMult = new Product(getNextPBarcode(), "3_mult sig digit cur average test", "1",
    								"count", 3, 3);
    	Product pMax = new Product(getNextPBarcode(), "4_max cur average test", "1",
    								"count", 3, 3);
    	
    	p_f.addProduct(pInt);
		p_f.addProductToContainer(pInt, SU);
    	p_f.addProduct(pOne);
		p_f.addProductToContainer(pOne, SU);
    	p_f.addProduct(pMult);
		p_f.addProductToContainer(pMult, SU);
    	p_f.addProduct(pMax);
		p_f.addProductToContainer(pMax, SU);
		
		Item iInt1 = i_f.addItem(pInt, SU);
		
		Item iOne1 = i_f.addItem(pOne, SU);
		Item iOne2 = i_f.addItem(pOne, SU);
		
		Item iMult1 = i_f.addItem(pMult, SU);
		Item iMult2 = i_f.addItem(pMult, SU);
		Item iMult3 = i_f.addItem(pMult, SU);
		Item iMult4 = i_f.addItem(pMult, SU);
		
		Item iMax1 = i_f.addItem(pMax, SU);
		Item iMax2 = i_f.addItem(pMax, SU);
		Item iMax3 = i_f.addItem(pMax, SU);
		
		TimeSource.instance().setTime(day02);
		i_f.removeItem(iInt1); //1.0 days
		
		i_f.removeItem(iOne1);
		
		i_f.removeItem(iMult1);
		i_f.removeItem(iMult2);
		i_f.removeItem(iMult3);
		
		i_f.removeItem(iMax1);
		i_f.removeItem(iMax2);
		
		TimeSource.instance().setTime(day03);
		i_f.removeItem(iOne2); //1.5 days
		
		i_f.removeItem(iMult4); //1.25 days
		
		i_f.removeItem(iMax3); //1 1/3 days
		
		TimeSource.instance().reset();
		List<List<String>> data = runReport(MONTHS, day10);
		
		assertEquals(data.get(0).get(0), genTitle(MONTHS));
		
		List<String> pIntTest = data.get(1);
		assertEquals(pIntTest.get(0), "1_integer cur average test");
		assertEquals(pIntTest.get(8), "1 days /\n1 days");
		
		List<String> pOneTest = data.get(2);
		assertEquals(pOneTest.get(0), "2_one sig digit cur average test");
		assertEquals(pOneTest.get(8), "1.5 days /\n2 days");
		
		List<String> pMultTest = data.get(3);
		assertEquals(pMultTest.get(0), "3_mult sig digit cur average test");
		assertEquals(pMultTest.get(8), "1.2 days /\n2 days");
		
		List<String> pMaxTest = data.get(4);
		assertEquals(pMaxTest.get(0), "4_max cur average test");
		assertEquals(pMaxTest.get(8), "1.3 days /\n2 days");
    }
    
    @Test
    public void test13ghij() throws Exception {
    	final int MONTHS = 50;
    	int lastYear = now.get(Calendar.YEAR) - 1;
    	Calendar febFirst = makeCalendar(lastYear, Calendar.FEBRUARY, 1);
    	Calendar marFirst = makeCalendar(lastYear, Calendar.MARCH, 1);
    	double longDecimalDouble = 1.59839453853;
    	String longDecimalString = longDecimalDouble + "";
    	
    	TimeSource.instance().setTime(febFirst);
    	Product ld = new Product(getNextPBarcode(), "1_long decimal test", longDecimalString,
    								"pounds", 3, 3); //covers 13g
    	Product fo = new Product(getNextPBarcode(), "2_fluid ounces test", "8",
    								"fluid ounces", 3, 3); //covers 13h
    	Product nd = new Product(getNextPBarcode(), "3_no digits after barcode test", "1.",
    								"pounds", 3, 3); //covers 13i
    	Product re = new Product(getNextPBarcode(), "4_regular unit test", "5",
    								"liters", 3, 3); //covers 13j
    	
    	p_f.addProduct(ld);
		p_f.addProductToContainer(ld, SU);
    	p_f.addProduct(fo);
		p_f.addProductToContainer(fo, SU);
    	p_f.addProduct(nd);
		p_f.addProductToContainer(nd, SU);
    	p_f.addProduct(re);
		p_f.addProductToContainer(re, SU);
		
		TimeSource.instance().reset();
		List<List<String>> data = runReport(MONTHS, marFirst);
		
		assertEquals(data.get(0).get(0), genTitle(MONTHS));
		
		List<String> ldTest = data.get(1);
		assertEquals(ldTest.get(0), "1_long decimal test");
		String rounded = ((double)Math.round(longDecimalDouble * 10) / 10) + "";
		assertEquals(ldTest.get(2), rounded + " pounds");
		
		List<String> foTest = data.get(2);
		assertEquals(foTest.get(0), "2_fluid ounces test");
		assertEquals(foTest.get(2), "8 fluid ounces");
		
		List<String> ndTest = data.get(3);
		assertEquals(ndTest.get(0), "3_no digits after barcode test");
		assertEquals(ndTest.get(2), "1 pounds");
		
		List<String> reTest = data.get(4);
		assertEquals(reTest.get(0), "4_regular unit test");
		assertEquals(reTest.get(2), "5 liters");
    }
    
    @Test
    public void test6abc() throws Exception {
    	leapTest(2012); //meets needs of 6
    }
    
    @Test
	public void test7abc() throws Exception {
		leapTest(2000); //meets needs of 7
	}

	@Test
    public void test8ab() throws Exception {
    	final int MONTHS = 50;
    	int expectedDays = 0;
    	int curYear = now.get(Calendar.YEAR);
    	Calendar febFirst = makeCalendar(curYear, Calendar.FEBRUARY, 1);
    	Calendar marFirst = makeCalendar(curYear, Calendar.MARCH, 1);
    	Calendar marHalf = makeCalendar(curYear, Calendar.MARCH, 15);
    	
    	TimeSource.instance().setTime(febFirst);   	
    	Product fp = new Product(getNextPBarcode(), "testFeb", "1", "count", 3, 3);
    	p_f.addProduct(fp);
		p_f.addProductToContainer(fp, SU);
		Item fi1 = i_f.addItem(fp, SU); //covers 8a
		Item fi2 = i_f.addItem(fp, SU);
		Item fi3 = i_f.addItem(fp, SU);
    	
    	TimeSource.instance().setTime(marFirst);
    	Product mp = new Product(getNextPBarcode(), "testMar", "1", "count", 3, 3);
    	p_f.addProduct(mp);
		p_f.addProductToContainer(mp, SU);
		Item mi1 = i_f.addItem(mp, SU); //covers 8b
		Item mi2 = i_f.addItem(mp, SU);
		Item mi3 = i_f.addItem(mp, SU);
		
		TimeSource.instance().setTime(marHalf);
		i_f.removeItem(fi1);
		i_f.removeItem(fi2);
		i_f.removeItem(fi3);
		i_f.removeItem(mi1);
		i_f.removeItem(mi2);
		i_f.removeItem(mi3);	
    	
    	TimeSource.instance().reset();
    	List<List<String>> data = runReport(MONTHS);
    	
    	assertEquals(data.get(0).get(0), genTitle(MONTHS));
    	
    	//feb
    	List<String> febArray = data.get(1);
    	expectedDays = daysBetween(febFirst, marHalf);
    	assertEquals(febArray.get(0), "testFeb");
    	assertEquals(febArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
    	
    	//mar
    	List<String> marArray = data.get(2);
    	expectedDays = daysBetween(marFirst, marHalf);
    	assertEquals(marArray.get(0), "testMar");
    	assertEquals(marArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
    }
    
    @Test
    public void test5c9c11c() throws Exception {
    	final int NUM_PRODUCTS = 10;
    	final int DESC_LEN = 15;
    	final int MONTHS = rand.nextInt(98) + 2; //covers 9c
    	Calendar cal = Calendar.getInstance();
    	cal.set(Calendar.DAY_OF_MONTH, 29); 
    	TimeSource.instance().setTime(cal); //covers 5c
    	
    	//add NUM_PRODUCTS with a random description
    	Iterator<String> stringIter = addProductsWithRandDesc(SU, NUM_PRODUCTS, DESC_LEN);
    	
    	//run the stats report
    	List<List<String>> data = runReport(MONTHS);
    	
    	//make sure the title is there
    	assertEquals(data.get(0).get(0), genTitle(MONTHS));
    	
    	//ensure the descriptions are in the right order, covers 11c
    	for (int row = 1; row <= NUM_PRODUCTS && stringIter.hasNext(); row++) {
    		assertEquals(data.get(row).get(0), stringIter.next());
    	}
    }
    
    private void leapTest(int year) throws Exception {
		
		final int YEAR = year;
		final int MONTHS = 50;
		int expectedDays = 0;
		
		assertTrue(isLeapYear(YEAR));
		
		Calendar febFirst = makeCalendar(YEAR, Calendar.FEBRUARY, 1);
		Calendar beforeLeap = makeCalendar(YEAR, Calendar.FEBRUARY, 28);
		Calendar onLeap = makeCalendar(YEAR, Calendar.FEBRUARY, 29);
		Calendar afterLeap = makeCalendar(YEAR, Calendar.MARCH, 1);
		Calendar aprilFirst = makeCalendar(YEAR, Calendar.APRIL, 1);
		
		TimeSource.instance().setTime(febFirst);
		Product blp = new Product(getNextPBarcode(), "1_beforeLeapTest", "1", "count", 3, 3);
		p_f.addProduct(blp);
		p_f.addProductToContainer(blp, SU);
		
		Product atlp = new Product(getNextPBarcode(), "2_atLeapTest", "1", "count", 3, 3);
		p_f.addProduct(atlp);
		p_f.addProductToContainer(atlp, SU);
		
		Product aflp = new Product(getNextPBarcode(), "3_afterLeapTest", "1", "count", 3, 3);
		p_f.addProduct(aflp);
		p_f.addProductToContainer(aflp, SU);
		
		Item bli = i_f.addItem(blp, SU);
		Item atli = i_f.addItem(atlp, SU);
		Item afli = i_f.addItem(aflp, SU);
		
		TimeSource.instance().setTime(beforeLeap);
		i_f.removeItem(bli); //covers a
		
		TimeSource.instance().setTime(onLeap);
		i_f.removeItem(atli); //covers b
		
		TimeSource.instance().setTime(afterLeap);
		i_f.removeItem(afli); //covers c
		
		List<List<String>> data = runReport(MONTHS, aprilFirst);
	
		List<String> beforeArray = data.get(1);
		assertEquals(beforeArray.get(0), "1_beforeLeapTest");
		expectedDays = daysBetween(febFirst, beforeLeap);
		assertEquals(beforeArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
		
		List<String> onArray = data.get(2);
		assertEquals(onArray.get(0), "2_atLeapTest");
		expectedDays = daysBetween(febFirst, onLeap);
		assertEquals(onArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
		
		List<String> afterArray = data.get(3);
		assertEquals(afterArray.get(0), "3_afterLeapTest");
		expectedDays = daysBetween(febFirst, afterLeap);
		assertEquals(afterArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
	}

	private Iterator<String> addProductsWithRandDesc(Container SU, int count, int len)
			throws Exception {
		SortedSet<String> strings = new TreeSet<String>();
		while(strings.size() < count) {
			strings.add(genRandString(len));
		}
		
		//randomize the adding order
		List<String> descList = new ArrayList<String>(strings);
		Collections.shuffle(descList);
		
		for (int i = 0; i < count; i++) {
			Product p = new Product(getNextPBarcode(), descList.get(i), "1", "count", 3, 3);
			p_f.addProduct(p);
			p_f.addProductToContainer(p, SU);
		}
		
		return strings.iterator();
	}
    
    private List<List<String>> runReport(int months) throws Exception {
    	return runReport(months, now);
    }
    
    private List<List<String>> runReport(int months, Calendar date) throws Exception {
    	TestBuilder testB = new TestBuilder(genTitle(months));
    	TimeSource.instance().setTime(date);
    	ProductStatsVisitor visitor = new ProductStatsVisitor(testB, months);
    	ProductManager.instance().accept(visitor);
    	testB.print();
    	return testB.getData();
    }

	private String genRandString(int len) {
    	char[] text = new char[len];
    	for (int i = 0; i < len; i++) {
    		text[i] = ALPHA.charAt(rand.nextInt(ALPHA.length()));
    	}
    	return new String(text);
    }
    
    private String genTitle(int months) {
    	return "Product Report (" + months + " Months)";
    }
    
    private String getNextPBarcode() {
    	String barcode = "barcode" + PbarcodeCounter;
    	PbarcodeCounter++;
    	return barcode;
    }
    
    /*
     * TAs: this single method saves me almost 100 lines of code.  We could pretend that I've
     * inlined this function everywhere I've used it if that's what you're into.
     */
    private Calendar makeCalendar(int year, int month, int day) {
    	Calendar cal = Calendar.getInstance();
    	cal.set(Calendar.YEAR, year);
    	cal.set(Calendar.MONTH, month);
    	cal.set(Calendar.DAY_OF_MONTH, day);
    	return cal;
    }
    
    private int daysBetween(Calendar c1, Calendar c2) {
    	return DateUtils.daysBetween(c1, c2);
    }
    
    private boolean isLeapYear(int year) {
    	if (year % 100 == 0) {
    		return (year % 400 == 0);
    	}
    	else {
    		return (year % 4 == 0);
    	}
    }

}
